// Copyright 2012 Ray Lambert <codemonkey@interthingy.net>
// Licensed under GPLv3+ or later (see COPYING)
// hosted at: http://code.google.com/p/argent/

#include "regex.h"
#include "mkstr.h"
#include <sstream>
#include <stdexcept>
#include <cstring>
#include <cctype>

#include <iostream>

bool regex::throw_error(int rc) const
{
    static const int bufsize = 1024;
    char buf[bufsize];
    regerror(rc, &exp, buf, bufsize);
    throw std::runtime_error( std::string(buf) );
    return false;
}

regex::regex(const std::string &s, unsigned flags/*=0*/)
{
    std::memset(&exp, 0, sizeof(&exp) );
    int f = 0;
    if ( flags & extended )
        f |= REG_EXTENDED;
    if ( flags & nocase )
        f |= REG_ICASE;
    int rc = regcomp(&exp, s.c_str(), flags);
    if ( rc != 0 )
        throw_error(rc);
}

regex::~regex()
{
    regfree(&exp);
}

bool regex::exec(
    const std::string &s, 
    results &r, 
    std::string::size_type ofs/*=0*/) const
{
    const int nmatches = 10;
    regmatch_t matches[nmatches];
    r.clear();
    int rc = regexec(&exp, s.c_str() + ofs, nmatches, matches, 0);
    if ( rc == REG_NOMATCH )
        return false;
    if ( rc != 0 )
        return throw_error(rc);
    for(int i=0; i < nmatches; ++i)
    {
        if ( matches[i].rm_so == -1 )
            break;
        matches[i].rm_so += ofs;
        matches[i].rm_eo += ofs;
        r.push_back( matches[i] );
    }
    return true;
}

bool regex::exec(
    const std::string &s, 
    std::vector<std::string> &r, 
    std::string::size_type ofs/*=0*/) const
{
    results tr;
    bool rc = exec(s,tr,ofs);
    if (rc)
    {
        for(auto it = tr.begin(); it != tr.end(); ++it)
        {
            std::string rs = s.substr(it->rm_so, it->rm_eo - it->rm_so);
            r.push_back(rs);
        }
    }
    return rc;
}

/*static*/ std::string regex::subst(
    const std::string &s, 
    const std::vector<std::string> &matches,
    const std::string &rep) 
{
    enum { normal, upper, lower, word } mode = normal;
    std::ostringstream os;
    for(std::string::size_type i = 0; i < rep.length(); ++i)
    {
        char c = rep.at(i);
        if ( c != '\\' )
        {
            os << c;
            continue;
        }
        ++i;
        if ( i >= rep.length() )
        {
            os << c;
            break;
        }
        c = rep.at(i);
        if ( std::isdigit(c) )
        {
            unsigned i = c - '0';
            if ( i >= matches.size() )
                throw std::runtime_error( "invalid group reference: " + mkstr(i) );
            std::string ss = matches[i];
            if ( not ss.empty() )
            {
                switch(mode)
                {
                    case upper:
                    {
                        for(std::string::iterator it=ss.begin(); it != ss.end(); ++it)
                            os << char( std::toupper( *it ) );
                        mode = normal;
                        break;
                    }
                    case lower:
                    {
                        for(std::string::iterator it=ss.begin(); it != ss.end(); ++it)
                            os << char( std::tolower( *it ) );
                        mode = normal;
                        break;
                    }
                    case word:
                    {
                        int alphas = 0;
                        for(std::string::iterator it=ss.begin(); it != ss.end(); ++it)
                        {
                            if ( std::isalpha( *it ) )
                            {
                                ++alphas;
                                if ( alphas == 1 )
                                    os << char( std::toupper( *it ) );
                                else
                                    os << char( std::tolower( *it ) );
                            }
                            else
                            {
                                alphas = 0;
                                os << *it;
                            }
                        }
                        mode = normal;
                        break;
                    }
                    default:
                    {
                        os << ss;
                        break;
                    }
                }
            }
        }
        else
        {
            switch(c)
            {
                case 'U': mode = upper; continue;
                case 'L': mode = lower; continue;
                case 'W': mode = word;  continue;
                case 'n': c = '\n'; break;
                case 'r': c = '\r'; break;
                case 't': c = '\t'; break;
                case 'f': c = '\f'; break;
                case 'b': c = '\b'; break;
                case 'z': c = '\0'; break;
                default: break;
            }
            os << c;
        }

    }
    return os.str();
}
/*static*/ std::string regex::subst(
    const std::string &s, 
    const results &res, 
    const std::string &rep, 
    bool whole/*=false*/)
{
    if ( res[0].rm_so == -1 )
        return whole ? s : "";

    std::ostringstream os;

    // add part before match if outputting whole string
    if ( whole and res[0].rm_so > 0 )
        os << s.substr(0,res[0].rm_so);

    // add replacement string
    std::vector<std::string> matches;
    for(auto it = res.begin(); it != res.end(); ++it)
    {
        std::string rs = s.substr(it->rm_so, it->rm_eo - it->rm_so);
        matches.push_back(rs);
    }
    os << subst(s,matches,rep);

    // add part after match if outputting whole string
    if ( whole and res[0].rm_eo < int( s.size() ) )
        os << s.substr(res[0].rm_eo);

    return os.str();
}

